--- title: "09-项目笔记(七) - API 开放项目项目教程 - 编程导航教程" created: 2025-12-02 tags: - 项目 aliases: - 项目笔记(七) - API 开放项目项目教程 - 编程导航教程 --- # 项目笔记(七) - API 开放项目项目教程 - 编程导航教程 ## 一、项目概述 本项目是一个面向开发者的 API 平台,提供 API 接口供开发者调用。用户通过注册登录,可以开通接口调用权限,并可以浏览和调用接口。每次调用都会进行统计,用户可以根据统计数据进行分析和优化。管理员可以发布接口、下线接口、接入接口,并可视化接口的调用情况和数据。本项目侧重于后端,涉及多种编程技巧和架构设计层面的知识。 ## 二、本期时间点 ![[2rhpNfIuHo8Xh0Ea-806b93e4.webp]] ## 三、本期计划(时间点 23:20-25:46) 1. 完成网关业务逻辑 2. 开发管理员分析的功能 3. 上线 ## 四、后端网关业务(时间点 25:46-01:57:51) ### 1. 梳理网关业务逻辑(时间点 25:46-27:14) **以下操作可以复用:** 1. 实际情况应该是去数据库中查是否已分配给用户秘钥(ak、sk是否合法) 2. 1. 先根据 accessKey 判断用户是否存在,查到 secretKey 2. 对比 secretKey 和用户传的加密后的 secretKey 是否一致 3. 从数据库中查询模拟接口是否存在,以及请求方法是否匹配(还可以校验请求参数) 4. 调用成功,接口调用次数 + 1 invokeCount **进一步说明:** 找到 yuapi-gateway 项目的 CustomGlobalFilter,看下有哪些业务逻辑是没有做的。 目前的实现中,我们在鉴权时是直接写死 "yupi",检查 accessKey 是否与用户匹配。 然而,更好的实践是从数据库中查询用户分配的 accessKey 并验证其有效性。 ![[1DM2zr6ZXhn4c6vR-c64cc580.webp]] 其次,就是我们要检查模拟接口是否存在,并验证请求方法是否匹配。 甚至还可以对用户的请求参数进行合法性校验。 ![[O04QAyfj52PYe60I-8bdb0048.webp]] 再次,如果用户调用模拟接口成功,需要统计接口的调用。 ![[Vnj4nysl8X5rwrXM-7ff1fdfb.webp]] 接下来我们来依次开发。 ### 2. 项目启动及问题思考(时间点 27:14-31:04) 启动 nacos,进入 nacos 中的 bin 目录; 选中上面的路径栏,输入 cmd,按[Enter]回车进入。 ![[UUdyC51GvvGRAS0W-6cc7fb23.webp]] 把命令粘贴进去,先以单机模式运行,在 8848 端口启动起来了。 ```text # Linux/Unix/Mac sh startup.sh -m standalone # ubuntu bash startup.sh -m standalone # Windows startup.cmd -m standalone ``` ![[pmJXlbMdSPsgmQC7-cfb3ee7e.webp]] 启动 redis 之后,以 debug 模式启动 yuapi-backend 项目。 ![[UG7FoIn15VNkMMyr-d71ea4d9.png]] 点击 ![[pxJ2JYIaVw4qHukU-8d874c33.png]]按钮,可以看这个项目打的断点。 ![[8c3DMqyjdjRO4Gur-aa206098.webp]] 把之前整的取消掉。 ![[jFJAJvIwAKaAUtVY-4bea4367.webp]] 再以 debug 模式启动 yuapi-gateway 项目,有结果就行,报错没关系。 点击 ![[pxJ2JYIaVw4qHukU-8d874c33.png]]按钮,看这个项目打的断点。 ![[wIgBhjQGNl0nED4q-d9e4f485.webp]] 把之前打的断点取消掉。 ![[IWUHIovcMLMOwIrZ-b6d7cd8f.webp]] 首先,从数据库中查是否已分配给用户秘钥,这代码逻辑应该在哪写呢? 之前我们的鉴权写在了模拟接口项目,用 idea 单独打开 yuapi-interface 项目。 ![[xdcD92jbXOSYcuBQ-e1f325d2.webp]] 找到之前校验用户 API 签名的那段代码,这里我们是直接写在单个具体的方法里。 ![[94beHi3htMAvZEy0-0a6825f6.webp]] 目前我们面临一个问题,就是项目尚未接入数据库,也并未引入 MyBatis。 我们不能指望开发人员自行引入 MyBatis 并调用我们的公共数据库,这显然是不切实际的。 因此,应该为开发人员提供一个远程调用的服务,以便帮助他们进行验证,或者我们可以提供一个 SDK 供他们引入,以便进行验证操作。在这种情况下,我们需要依赖一个公共的服务来实现这个功能。 ### 3. 抽象公共服务(时间点 31:04-35:20) **项目名:**yuapi-common **目的:**让方法、实体类在多个项目间复用,减少重复编写。 **服务抽取:** 1. 数据库中查是否已分配给用户秘钥(根据 accessKey 拿到用户信息,返回用户信息,为空表示不存在) 2. 从数据库中查询模拟接口是否存在(请求路径、请求方法、请求参数,返回接口信息,为空表示不存在) 3. 接口调用次数 + 1 invokeCount(accessKey、secretKey(标识用户),请求接口路径) **步骤:** 1. 新建干净的 maven 项目,只保留必要的公共依赖 2. 抽取 service 和实体类 3. install 本地 maven 包 4. 让服务提供者引入 common 包,测试是否正常运行 5. 让服务消费者引入 common 包 **进一步说明:** 接下来,我们计划创建一个公共服务模块,以便在多个项目之间实现方法和实体类的复用,从而避免重复编写相同的代码。 首先,我们需要明确哪些通用服务是必要的。先从数据库中查询是否已为用户分配了密钥,我们可以对这个方法进行进一步抽象,考虑需要传递哪些参数以及期望的返回值,具体来说,我们要查找数据库以确认是否已经将 accessKey 分配给了某个用户。 我们**需要关注哪些参数需要传递?**只需要 accessKey 参数,如果还有其他参数,可以在之后进行补充。 其次,我们需要处理的另一个问题是从数据库查询模拟接口是否存在。这个任务是网关模块的一部分,我们需要确保网关能够实现这个功能,具体来说,我们需要一个方法来检查数据库中是否存在特定的模拟接口。 **这个方法需要接收哪些参数呢?**找到网关项目 yuapi-gateway。 ![[tPbldpMVUYhkWxS1-6e542bcc.webp]] 这里的第四步,我们既然是要查模拟接口是否存在,是不是要传递这个接口的信息。 ![[hK0hquYomdIU7gFU-0490f618.webp]] 这**接口信息是什么?**是从请求路径和方法中获取到的,甚至你还可以传请求参数; 所以这里的话我们可以封装一个对象。 ![[UfKAu8QPO80LSMdO-4ebf64d5.webp]] 第三个就是对接口调用次数进行加一操作。**这个过程需要传递什么参数呢?** 考虑到要将调用次数加一,必须要知道是哪个用户进行了接口调用,以及具体是哪个接口的调用次数要增加。在这里,可以借助 accessKey 和 secretKey 这两个密钥来标识用户并获取其信息。 因此,我们需要传递的参数包括 accessKey 和 secretKey。通过 secretKey,我们可以识别用户并获取相关信息。此外,还需要传递请求的接口路径。暂定这些,后续需要进行调整,也可以进行修改。 ### 4. 创建公共服务(时间点 35:20-01:15:43) #### 4.1 创建公共服务项目(时间点 35:20-40:36) 建一个 Maven 项目,提供公共的接口、实体类; 点击 File → New。 ![[Cg6mvF8cj8jqyK9l-eeecbead.webp]] 创建 Maven 项目,选择 1.8 版本,点击 Next。 ![[lKyH6NIiv3DzoWDt-90d21bab.webp]] 项目名`yuapi-common`。 ![[VxTk4E4RVg6Kit2t-e0ba3b82.webp]] 选择在新窗口打开。 ![[FF9mUrMA2om6jx3Z-2747b607.png]] 重新设置新项目的 Maven 仓库。 ![[07ucyONbW1uikleM-f82291e7.webp]] 在 java 目录下新建包。 我们要将服务之间进行调用的几个方法写到公共类中,以及相应的 model,就看需要什么就传什么。 ![[sRRsSchSY2qTqzaG-0f1ee744.webp]] 回到 yuapi-backend 项目,查看 invokeCount 方法,它这里接收接口的 id 和用户 id,我们只要拿到这两个即可。 ![[idBXGZ8vNAettYTd-286c86cc.webp]] 我们就用它这个方法,复制 UserInterfaceInfoService.java,粘贴到 yuapi-common 项目中的 service 包下,还爆红了,因为实体类不存在,还要复制实体类。 ![[Oro5KBK3zF4hl3B7-e040ac58.webp]] 复制 yuapi-backend 项目中的 model 包,粘贴到 yuapi-common 项目中。 ![[nnIFwZz41QeV9CgL-0f04f8d2.webp]] ![[jMvn0OYAOHn3Rf2G-7c572d59.webp]] 把 dto 包删掉,因为它就是类与类之间的转换,不需要。 ![[qGBCRtoMzHURQqkD-f79c0256.webp]] 在公共服务项目引入 yuapi-backend 项目的依赖,删除多余的依赖,这个公共服务只负责提供接口,不负责提供实现。 ```xml org.mybatis.spring.boot mybatis-spring-boot-starter 2.2.2 com.baomidou mybatis-plus-boot-starter 3.5.1 com.google.code.gson gson 2.9.0 org.apache.commons commons-lang3 3.12.0 mysql mysql-connector-java runtime org.springframework.boot spring-boot-configuration-processor true org.projectlombok lombok true junit junit 4.13.2 test ``` 粘贴到 pom.xml 中。 ![[z6QRP94P7aTCrA0S-8fd67d70.webp]] 把 SpringBoot 的声明也粘贴进来,点击加载。 ps.这个声明相当于定义了一些依赖的版本、信息。 ![[WEc7s3uDTLVM4bET-9da0a9f5.webp]] ![[HZ66B2alWuntnkhW-a1c88c18.webp]] model 包下就不爆红了。 ![[95R05JiXqLCJrNAO-1df55e71.png]] 来到 PostVO,删掉上面的引入,让它重新加载。 ![[8MgGP6c5fl5NBPmm-d14ec96c.webp]] ![[mjePRxk9K7l5hYEV-ba5c7629.webp]] 回到 UserInterfaceInfoService 中,删掉上面的引入,让它重新加载。 ![[bCE4cYvCqPEKRxMr-3b692def.webp]] 重新加载后就不爆红了。 ![[zd3YFbrfmGoCiz8t-edf008bf.webp]] #### 4.2 编写方法(时间点 40:36-54:17) 在 UserInterfaceInfoService 定义两个方法。 ![[rDJuAJ8X2KPbtjVr-aafb1cbe.webp]] 我们不应该将所有的服务都集中在 UserInterfaceInfoService 中,应该将它们分成多个独立的服务进行编写; 复制 yuapi-backend 项目的 InterfaceInfoService.java、UserService.java。 ![[HIfMo4GfVrL9Jmpz-96764a92.webp]] 粘贴到 yuapi-common 项目中。 ![[AsFPkWvjeVr0nH8m-fd0ab139.webp]] 这两个文件还有爆红,删除引入,让它重新加载。 ![[udoBgf4f0mBkywXW-5ee647ac.webp]] ![[CA173Gd64Bqu0cfl-ec8a8581.webp]] ![[RusmUpp05s7cKFpA-97ca7aa5.webp]] ![[xcGNbxSCHxvOi8kn-a39e1d51.webp]] 把 UserService 里面的用户注册、登录... 删掉,不需要。 ![[WVPplDguJvZXWSdm-3622b42f.webp]] 来**写两个方法:** 1. 数据库中查是否已分配给用户秘钥(accessKey) 2. 从数据库中查询模拟接口是否存在(请求路径、请求方法、请求参数) 首先,在 UserService 中写数据库中查是否已分配给用户秘钥的方法。 ![[F616PwhziYiN1VsK-44a75482.webp]] 其次,在 InterfaceInfoService 写从数据库中查询模拟接口是否存在的方法。 ![[ZUHSkJasdYCcrw4z-685af170.webp]] 给以下两个文件统一加上`Inner`的文件名前缀。 ![[vd0OzNAF3bHPh9nx-8cdf8f69.webp]] ![[z95VEXJZA1BFe7Or-e0ba21a6.png]] 删除多余的内容,没有继承它,修改后的三个文件: ![[DsN4mOY5p7RJdK1i-b0b617d9.webp]] ![[nfxI3VMpkcmdZFi3-3c9f49ab.webp]] ![[ZYzcvtbZCmIn67Tb-d279772b.webp]] 把 yuapi-common 项目打包,先设置它的版本为`0.0.1`。 ![[iuVSsRsYgfjowi57-64eca481.webp]] 点击右侧 Maven,双击 install 进行打包。 ![[iWDy6mYt7uKf2o3q-6dcab6c9.webp]] 打包成功。 ![[8GiawG7ryfKKTwGs-ed8b3725.webp]] #### 4.3 引入公共服务依赖(时间点 45:02-58:22) 复制公共服务版本信息。 ![[R9gjm7009agKKClX-186cc007.webp]] ```xml com.yupi yuapi-common 0.0.1 ``` 粘贴到 yuapi-backend 项目中。 ![[N55QGQT6OsxPMTlR-62d9eaf6.webp]] --- **💭** **题外话:** 在学习 Dubbo 的过程中,建议大家在学完 Spring Boot 和 Redis 之后开始学习。如果时间比较紧张,可以选择暂时不深入学习 Dubbo,但如果有半年或者更长时间来学习,建议在进入 Spring Cloud 之前先学习 Dubbo。这是因为 Spring Cloud 微服务架构的底层可能会使用 HTTP 协议或其他协议进行远程调用,微服务涉及到更多的知识领域,例如网关、分布式事务以及服务总线等多个概念。 而 Dubbo 作为一个 RPC 调用框架,更加专注于远程调用的领域,相对更为纯粹。因此,在掌握 Dubbo 的基础之后,再深入学习 Spring Cloud 将会更有帮助。 --- 验证一下,看看 yuapi-backend 项目能不能用这个公共的类了; 把 UserInterfaceInfoService.java、UserInterfaceInfo.java 删掉。 ![[Iw8KY17IhDQiJCEA-1af499ee.webp]] 更换引入。 ![[v6ceitsssiob7V3J-875f7270.webp]] ![[uSLFoQ58e6uqVlXc-5cafae1b.webp]] ![[dAFVx6bVDBvtRcXF-36d506aa.webp]] ![[7Gi7SvEm8ocjAVcr-716daa55.webp]] ![[OLcEUB4j1oEeFfUN-e029cbe8.webp]] ![[74TSuUnrzARtt5pp-e659e6c6.webp]] ![[yNAPkobhfiXfUqEt-43119142.webp]] ![[p2uNSJ2l3H1ChL4h-5f39f74b.webp]] ![[dsU1k7QcO3Cx26WS-87b38f9a.webp]] ![[aZhPwP4OycpnmcWc-c012970b.webp]] ![[z5WnUXRvtpqvJ12Z-43c8a3dc.webp]] ![[NDXlcOgw1RGubtl1-dec7394d.webp]] ![[6SnYAKRZ1btfRkd1-c0ed3eef.webp]] ![[ssbwcZFqPIcqunMF-9b8ab5a6.webp]] ![[q4fs5T9pe0B2Lia9-289e4655.webp]] ![[5Yq57m5t8Ngge6h8-7a85e438.webp]] 把这个 User、InterfaceInfo 删掉,直接调用公共服务里的。 ![[AP6lAyncK6ebr4kD-23360cbe.webp]] 执行测试类,看看能不能运行成功。 ![[HntVQnkeDUWKuOf2-dc56437e.webp]] 调用成功,也输出的 SQL 语句。 ![[i5FULGkPSnc1BXNO-bdb35540.webp]] 现在公共服务算是抽取完成了。 #### 4.4 方法实现(时间点 58:22-01:15:43) 接下来就去实现刚刚的那几个方法,点这里,找一下 yuapi-common 依赖。 ![[IHOWM28X3avnV40t-c9910047.webp]] 找到 InnerUserService,来实现接口; 光标放到 InnerUserService,按[Alt+Enter],选择`Implenment interface`(实现接口)。 ![[BwOF6rT3VXtZKy6G-3c12047e.webp]] 选择目标目录。 ![[klZwyabOsEQbcibJ-fa3fb577.webp]] 实现方法就选 getInvokeUser。 ![[maGMgYBFnBny9v1J-7230e3a5.webp]] 它就生成在我们指定的目录下了。 ![[w5LG4UbG43aBpI3J-7ac0a98f.webp]] 再实现 InnerInterfaceInfoService 的接口; 光标放到 InnerInterfaceInfoService,按[Alt+Enter],选择 Implenment interface。 ![[yMiK7HvTVea9bQux-2c39a15f.webp]] 选择目标目录。 ![[ksTt9OwCZ2okcBfd-bd85b2ae.webp]] 实现方法选 getInterfaceInfo。 ![[vfEK5P7aMbmGIt2L-59d7e2c4.webp]] 生成好了。 ![[wfFj7fsqjFQ5ilqA-d45019bc.webp]] 再生成另一个,光标放到 InnerUserInterfaceInfoService; 按[Alt+Enter],选择 Implenment interface。 ![[xAvPbY5aiaY3x4zi-10261ead.webp]] 选择目标目录。 ![[zgDInebhp3Stl5Lk-34dfaa81.webp]] 实现方法选 invokeCount。 ![[HAo2ymYTdEDSdG7G-3a07a04f.webp]] 生成好了。 ![[EzcY5MkNJGc6lmUd-0e548b16.webp]] 在实现类里,我们就可以编写对应的方法了。 先去写统计接口次数的这个类,直接引入 UserInterfaceInfoService,调用它的方法即可。 ```java package com.yupi.project.service.impl; import com.yupi.project.service.UserInterfaceInfoService; import com.yupi.yuapicommon.service.InnerUserInterfaceInfoService; import javax.annotation.Resource; public class InnerUserInterfaceInfoServiceImpl implements InnerUserInterfaceInfoService { @Resource private UserInterfaceInfoService userInterfaceInfoService; @Override public boolean invokeCount(long interfaceInfoId, long userId) { // 调用注入的 UserInterfaceInfoService 的 invokeCount 方法 return userInterfaceInfoService.invokeCount(interfaceInfoId, userId); } } ``` ![[7kMwoBtgysEnwWSm-3d0a63d4.webp]] 再去写下获取调用用户信息的类,直接去数据库中根据 ak、sk 判断获取。 ![[F1AJx8fw1StI223k-328a4dd7.webp]] ```java package com.yupi.project.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.yupi.project.common.ErrorCode; import com.yupi.project.exception.BusinessException; import com.yupi.project.mapper.UserMapper; import com.yupi.yuapicommon.model.entity.User; import com.yupi.yuapicommon.service.InnerUserService; import org.apache.commons.lang3.StringUtils; import javax.annotation.Resource; public class InnerUserServiceImpl implements InnerUserService { @Resource private UserMapper userMapper; /** * 实现接口中的 getInvokeUser 方法,用于根据密钥获取内部用户信息。 * * @param accessKey 密钥 * @return 内部用户信息,如果找不到匹配的用户则返回 null * @throws BusinessException 参数错误时抛出业务异常 */ @Override public User getInvokeUser(String accessKey) { // 参数校验 if (StringUtils.isAnyBlank(accessKey)) { throw new BusinessException(ErrorCode.PARAMS_ERROR); } // 创建查询条件包装器 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("accessKey", accessKey); // 使用 UserMapper 的 selectOne 方法查询用户信息 return userMapper.selectOne(queryWrapper); } } ``` ![[ErEDnqMVWLTCEu9S-f90e9345.webp]] 最后写下根据请求路径、方法名获取接口信息的类。 ![[eCTWq01loK0enpRU-3f79736b.webp]] ```java package com.yupi.project.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.yupi.project.common.ErrorCode; import com.yupi.project.exception.BusinessException; import com.yupi.project.mapper.InterfaceInfoMapper; import com.yupi.yuapicommon.model.entity.InterfaceInfo; import com.yupi.yuapicommon.service.InnerInterfaceInfoService; import org.apache.commons.lang3.StringUtils; import javax.annotation.Resource; public class InnerInterfaceInfoServiceImpl implements InnerInterfaceInfoService { @Resource private InterfaceInfoMapper interfaceInfoMapper; /** * 实现接口中的 getInterfaceInfo 方法,用于根据URL和请求方法获取内部接口信息。 * * @param url 请求URL * @param method 请求方法 * @return 内部接口信息,如果找不到匹配的接口则返回 null * @throws BusinessException 参数错误时抛出业务异常 */ @Override public InterfaceInfo getInterfaceInfo(String url, String method) { // 参数校验 if (StringUtils.isAnyBlank(url, method)) { throw new BusinessException(ErrorCode.PARAMS_ERROR); } // 创建查询条件包装器 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("url", url); queryWrapper.eq("method", method); // 使用 InterfaceInfoMapper 的 selectOne 方法查询接口信息 return interfaceInfoMapper.selectOne(queryWrapper); } } ``` ![[sU9dNbPM7vgqEVOW-242ed6fb.webp]] 这三个接口就写完了~ 大家还记得我们在**使用 Dubbo 时需要做哪些事情吗?** 参考下之前编写的`DemoService`,来看一下,它只需要在我们的实现类上添加`@DubboService`注解即可。 ![[gnWE7pA7tF35zOD6-93eba736.webp]] 现在我们将刚刚的三个接口都加上这个注解,使它们也能够正常使用 Dubbo。 ![[wqn09iWD4ZNIhQlA-83dbd4e8.webp]] ![[emIMACYONUnt0IVh-72e1f1ed.webp]] ![[6vYWyRea5dD6IMmm-21a68e86.webp]] 在 impl 包下新建`inner`包,把内部和外部都隔离开,将三个接口都放进去。 ![[2cGE6EIhTdRdNy99-a38a0569.png]] 这些服务基本上写好了。 ### 5. 调用公共服务(时间点 01:15:43-01:57:51) #### 5.1 在网关编写调用公共服务(时间点 01:15:43-01:28:52) 以 debug 模式重启 yuapi-backend、yuapi-gateway 项目、启动 yuapi-interface 项目。 访问 nacos 注册中心 [http://自己服务的](http://xn--zfr16x5khr6p0nn/) IP 地址:8848/nacos/index.html,都注册上了。 ![[wUKnsXXbM2ovFCfV-22cb0555.webp]] 如果发现 **yuapi-backend 项目启动失败:** 1. 网速问题,网速太卡了,导致注册超时,等一会,重新启动 2. 端口冲突,换个端口 ps.网速问题延伸,因为注册默认 3 秒,超过 3 秒即注册失败,可以去设置增加注册超时时间。 接下来就去网关项目中使用这些公共的服务,复制公共服务依赖。 ![[C975OEVIBXbks7PL-f3ca8bd5.webp]] ```xml com.yupi yuapi-common 0.0.1 ``` 粘贴到 yuapi-gateway 项目中。 ![[0mPZICt5xBK29OJI-4130985a.webp]] 进入到网关要操作的主业务流程里,先**引入三个类:** 获取用户是否存在、获取接口信息是否存在、统计接口调用次数。 ![[d39DbHXlWFPArzbc-07427d1a.webp]] 完成这步。 ![[Bc8NYAa0y2RlVMsh-2a4c0bef.webp]] 修改后: ![[7tDwlPc3ao5Ce6vz-4b4a00ae.webp]] 往下滑,完善这步。 ![[1TcCKGKVw35LPt67-862bbe7a.webp]] 修改后: ![[xZHdzD3lXFGc23fH-beec1e75.webp]] 往下滑,来整这步。 ![[LH1E0LomuMcoNPsV-cf5fea4e.webp]] 先提取下请求路径、方法。 ![[jvAxjHIExpotsnP6-733d7730.webp]] 然后修改: ![[phTsVPsOgsJlokTF-3ea8b469.webp]] --- 💡 **有同学提到了一种防止绕过网关的方法,即在网关上添加一个请求头。** 实际上,是否添加这个请求头并不是特别重要,之前已经和大家讨论过,我们的目标是为了实现流量染色。 如果要添加这个请求头,可以在请求中再添加一个固定的标识,例如"yuapi",并给它一个任意的值,然后在客户端进行判断。 但是目前考虑到实际影响不大,暂时不添加这个请求头了。因为如果添加的话,客户端还需要进行额外的判断,在每个模拟接口处添加逻辑或者编写一个公共方法,这会变得比较繁琐。 --- 往下滑,来整这步。 ![[3FVIV0UG2i9XC66D-0ddb2a89.webp]] 先添加两个参数:接口 id、用户 id。 ![[AeDjvlIfdLWUNO3b-0fdb9a5e.webp]] 然后去修改: ![[pIvQeoRIb4qUxgXz-4f57d50f.webp]] ps.在生产环境中,通常公司都会接一个报警系统,以确保一旦出现问题,能够立即触发报警机制。 #### 5.2 调用测试(时间点 01:28:52-01:57:51) 重启 yuapi-gateway 项目,有小报错没关系,能显示之前的示例结果即可。 ![[u1q91vwbacyVK802-e999b5e2.webp]] 打个断点。 ![[efenbzfCzb13YiBV-5fc1bda0.webp]] 访问 [http://localhost:8090/api/name/get?name=yupi,自动跳回](http://localhost:8090/api/name/get?name=yupi%EF%BC%8C%E8%87%AA%E5%8A%A8%E8%B7%B3%E5%9B%9E) yuapi-gataway 项目。 ![[5306UCUpJp9bXUVi-dc0865a0.png]] 按[F8]执行到这里,你会发现它的请求路径是 /api/name/get。 ![[RacFqncIXl1vjum8-13f52ab5.webp]] 按[F9]结束执行,这个有点问题,我们后端数据库存的是 [http://localhost:7529/name/user。](http://localhost:7529/name/user%E3%80%82) ![[dlBWLlchzr082EdT-5ca9289c.webp]] --- **临时问题:如何获取接口转发服务器的地址** **思路提供:**网关启动时,获取所有的接口信息,维护到内存的 hashmap 中;有请求时,根据请求的 url 路径或者其他参数(比如 host 请求头)来判断应该转发到哪台服务器、以及用于校验接口是否存在。 这里的参数比如说,在客户端调用接口时使用。你需要获取特定接口的信息,例如其中的主机(host)信息。在客户端发起请求时,你可以将这个主机信息添加到请求头中。这样,网关就能够获取到这个请求头。 **进一步说明:** 就是根据用户请求的地址,我们要找出需要转发的目标服务器地址。为了获取类似于 localhost:7529 这样的地址,我们需要进行一些步骤,**如何实现这一步骤呢?**建议从数据库中获取这些信息。这里提供一个参考方法,但值得注意的是,这一步不是必需的,可以根据个人情况决定是否采取这一步。 **具体方法如下:**当我们启动网关项目时,首先从数据库中获取所有接口信息。对于每个 URL,我们需要获取与之关联的路径以及接口名称信息。之后,根据接口的其他相关信息,我们可以找到应该转发到的目标主机(host)。可以为数据库添加一个额外的字段,用于存储转发的目标服务器地址,通过这个地址,我们可以将请求转发到对应的路径上,这就是大致的流程。 --- 偷个懒,因为我们所有的模拟接口项目都位于同一个地址(8123),直接将这个地址进行拼接。 通常情况下,如果你自己在开发类似的模拟接口平台,也可以像这里一样,事先写入几个固定的地址,避免每次都需要从数据库中读取的繁琐过程。 写一个地址的常量,然后将其拼接。 ![[HXuBTTcDVHe2e1sO-3072f3f5.webp]] 重启 yuapi-gateway 项目,打开 yuapi-client-sdk 项目; 这个项目也要去引入公共服务。 ![[A7K2IsXKPFvrwSch-5fbbe219.webp]] --- 💡 有同学问:**如何让其他用户上传自己编写的接口?** 需要提供一个注册机制。在这个机制下,其他用户可以上传他们自己编写的接口信息。为了简化流程,可以设计一个用户友好的界面。在这个界面上,用户可以输入他们的接口信息,包括服务器地址(host)、请求路径等内容。也可以规定,在接入我们的平台时,用户必须使用我们提供的 SDK 或遵循一定的要求。 **如何进行接入和要求的遵循?**在用户上传接口的时候,我们需要对接口信息进行测试调用,以确保接口的正常运行,这可以通过我们的平台来完成。同时,我们也可以要求用户标明该接口是否是由我们的网关调用,这可能需要用户在代码中加入判断代码,或者引入我们提供的 SDK 来实现。 **接口信息的组织和存储:**当用户上传接口信息时,这些信息将被存储在 InterfaceInfo 接口中。除了 URL 外,还应该添加一个 host 字段,用于明确区分不同服务器的地址。这样,可以更清晰地区分请求路径和服务器地址,提高接口信息的可读性和可维护性。 --- 我们回到前端去调用,不在浏览器调用了,这样的话密码也绕不过去。 启动前端项目,访问 [http://localhost:8000,登录,就调用第一个接口,点击`查看`。](https://www.codefather.cn/course/1790979723916521474/section/1790987424616722433?type=#heading-0http://localhost:8000%EF%BC%8C%E7%99%BB%E5%BD%95%EF%BC%8C%E5%B0%B1%E8%B0%83%E7%94%A8%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%8E%A5%E5%8F%A3%EF%BC%8C%E7%82%B9%E5%87%BB%60%E6%9F%A5%E7%9C%8B%60%E3%80%82) ![[Uv1bH4unu5DgG6rY-9c2a12da.webp]] 输入请求参数后,点击调用。 ```json {"username":"yupi"} ``` ![[MnyLMRzzOc7CLaEf-b16535ef.webp]] 自动跳回 yuapi-gateway 项目。 ![[t0qImr9UtzAisGAZ-85e83943.webp]] 按[F8]向下执行到这里,请求来源是 127.0.0.1。 ![[RB6hQdJOK7JF2ON8-55b8c388.webp]] 按[F8]向下执行到这里,参数都拿到了。 ![[4Zdi89bZSCqaA9SE-a9e69095.webp]] 再往下,就是远程调用了,在 yuapi-backend 项目打个断点。 ![[GnWB7PvmTipaMtf2-f40991f3.webp]] 回到 yuapi-gateway 项目按[F8],就会跳到我们刚刚在 yuapi-backend 项目打断点的地方。 ![[hkhXnSMwV1Adtm9y-2f0e9a1a.webp]] 按[F8]跳到下一行,再按[F9]结束执行,然后把 yuapi-backend 项目断点取消。 ![[Dih44UnUvi46qaGC-523aa554.webp]] 回到 yuapi-backend 继续按[F8]向下执行。 ![[RP0sTvlkrHyO5emM-2ad14508.webp]] 执行到这里,发现它显示没有查到,按[F9]结束执行。 ![[IsD0ydvljzmeIeUe-683dfd02.webp]] 把这里的断点取消。 ![[EGBr621kcqDAF1Ug-5dee1481.webp]] 往下滑,在这里打个断点。 ![[lO01SQ6BNZiwTxTP-9bdd5daf.webp]] 去数据库人工修改下 url 的数据为 [http://localhost:8123/api/name/user,先把整个流程跑通。](http://localhost:8123/api/name/user%EF%BC%8C%E5%85%88%E6%8A%8A%E6%95%B4%E4%B8%AA%E6%B5%81%E7%A8%8B%E8%B7%91%E9%80%9A%E3%80%82) ![[pw0pIR3oLF7XC2zt-4ce22e90.webp]] 不过啊,因为我们登录的是 yupi 账号,如果登录其他账号就会报无权限错误,为什么呢? 因为我们之前的接口逻辑是写在模拟接口项目里的,我们现在的校验逻辑不应该写在这里; 注释掉,重启模拟接口项目。 ![[4POf3TzEbl1txdVC-3d41680d.webp]] 在前端重新点击`调用`,自动跳转回 yuapi-gateway 项目。 ![[x4uUdiQHmoeAxdOi-31a5edd2.webp]] 这次拿到了,按[F9]结束执行。 ![[79ITe1oUidWT8zly-b3db7ec3.webp]] 返回前端页面查看,成功输出。 ![[ikSU0ATYYRuYqwHb-b0bba5d7.webp]] 去数据库中查,也能看见次数的变化,现在整个业务流程跑通。 ![[MGAm8b3tXJVZqdCv-1ff96f49.webp]] ps.还有个小问题,我们的网关没有校验用户是否还有调用次数,这个一定要放在发送请求前,交给大家实现🐶。 ![[h1z74UjBER53inS1-f514c9f8.webp]] 我们回顾一下之前的业务流程,看一下用户是如何获取到他们的 AKSK 用于模拟调用接口的。 找到 InterfaceInfoController 中的 invokeInterfaceInfo 方法。 那么在这个方法中,我们是**如何获取 AKSK 的呢?** 首先,用户需要进行登录,我们就能从登录用户的信息中提取出他们的 AKSK。因此,可以说我们之前已经实现了动态的操作,能够在运行时获取用户信息并为他们分配密钥了。 ![[QfUUMsIr7hUIm5HV-e3a7d7b4.webp]] 所以 application.yml 这里写什么都无所谓了。 ![[eMsf4xgqmuEECjN2-96db0597.webp]] ## 五、统计分析功能(时间点 01:57:51-02:51:38) 接下来再给大家讲一个小的知识点,我们要去开发专门供管理员使用的统计分析功能,在实际开发过程中,并不需要去开发这个功能,根据实际需求而定。 ### 1. 需求分析(时间点 01:59:19-02:02:39) 各接口的总调用次数占比(饼图)取调用最多的前 3 个接口,从而分析出哪些接口没有人用(降低资源、或者下线),高频接口(增加资源、提高收费)。 **进一步说明:** **现在我们可以进行哪些分析呢?**基于我们当前的业务和数据,我们可以分析系统中的用户注册情况,即每日新增用户数量或总用户数,或者是,哪些接口被频繁调用、对于同一用户,他们使用的接口情况,例如某个用户今天调用了多少次接口。 **我们要做什么呢?**我们基于现有的数据,假设一些需求。例如,设想统计某个特定用户对接口的调用次数占比,可以采用饼图来呈现这个数据,通过图表,能直观地看到接口被调用的次数。 然而,在着手处理这个需求之前,我们必须要明确分析的目的是什么。我们一般进行分析都是为了达到某个特定的目标,就拿🐟的情况举例,之前主要从事运营分析和大数据分析,对诉求就是要求很高,所以我们必须要明确清楚分析诉求。 回到我们刚才提到的需求,以此为例,我们可以将其分解一下。这个需求的目的明确地是让某个用户,或者说用户本人,能够了解在某段时间内使用哪些接口的次数较多。**这样的分析有何意义呢?** 可以据此判断某个接口在特定时间内是否被频繁调用,如果某个接口在某段时间内几乎没有调用记录,那么**我们是否需要继续保持资源的分配呢?**另一方面,对于高频次调用的接口,我们可以考虑是否需要增加相应资源,或者在某些情况下进行收费等等。因此,这个需求背后是有很多的价值和意义的。 ### 2. 后端开发(时间点 02:02:39-02:21:46) #### 2.1 SQL 查询调用数据(时间点 02:02:39-02:07:26) 接下来,我们需要考虑如何实际实现这个需求,实现起来并不复杂。 这个实现涉及前后端的协同沟通,后端的任务相对简单,主要是涉及到数据库增删改查的操作,我们需要编写一个接口来获取示例数据,比如:“接口 A 被调用了两次,接口 B 被调用了三次”。 那么,**这个接口应该如何编写呢?**我们**需要获取哪些接口信息呢?**比如接口的名称、描述、地址等等。但是考虑到接口数量可能会很多,不可能全部展示出来,所以可以设置一个条件,只取前三个接口作为示例。首先,我们需要获取到每个接口的 interfaceInfoId,因为我们要按接口进行分组统计,这个统计操作涉及到了 user\_interface\_info 表。 让我们着手编写相应的 SQL 查询语句。基本上,我们需要按接口进行分组,然后统计每组接口的调用总数,这可以通过以下 SQL 查询实现: ```sql -- 获取接口调用次数的统计信息,并按照调用总次数降序排列,最后取前三个接口作为结果 select interfaceInfoId, sum(totalNum) as totalNum from user_interface_info group by interfaceInfoId order by totalNum desc limit 3; ``` 接下来,进行测试,先往数据库中添加一些数据,模拟不同接口被不同用户调用的情况,然后运行这个查询语句,查看结果是否符合预期。 ![[FhNcKDT4jhTbyOnw-83e5d2c6.webp]] --- 💡 **有同学说:有必要增加热点推荐功能吗?** 个人认为没有必要,我们不必过于复杂化这个系统,如果我们的接口数量很少,甚至可以在一个屏幕上展示出来,那么就没有必要考虑热点推荐功能。 --- 打开 SQL 控制台。 ![[X14RVREB7Q5nP73W-7071d841.webp]] ![[koJdm1HraAL4q0RN-87b2f5d4.png]] 将 SQL 语句粘贴进去,按[Ctrl+A]全选,点击绿色按钮执行。 ![[fHVTlusgKY8Ch5nj-82283c36.webp]] 下方显示查询结果。 ![[cVoBx8mgXx25XhJf-4fe70405.webp]] #### 2.2 业务层去关联查询接口信息(时间点 02:07:26-02:21:46) 在获取了这个 SQL 查询结果之后,**还有其他需要处理的事情吗?** 现在我们只拿到了接口的 id,但如果除了 id,还需要展示接口的名称,**应该如何处理呢?** 需要进行关联查询来获取接口信息。由于我们只取了前三,所以在这种情况下进行关联查询不会对性能产生太大影响,如果大家要展示所有接口,关联查询可能就会变得复杂。 回到 yuapi-backend 项目,新建一个 controller:AnalysisController。 ![[Xy3RtTjYK4Pupkqh-ff413bbd.webp]] 先写一点内容。 ![[OVjGkp1IPuKZBJ14-71c7f2eb.webp]] 再新建一个包装类,复制 PostVO.java,粘贴到 vo 包下,并重命名为`InterfaceInfoVO`。 ![[bCNR40XpuLXMsn7T-978567d0.png]] 然后修改一下 InterfaceInfoVO.java。 ```java package com.yupi.project.model.vo; import com.yupi.yuapicommon.model.entity.InterfaceInfo; import lombok.Data; import lombok.EqualsAndHashCode; /** * 接口信息封装视图 * * @author yupi * @TableName product */ @EqualsAndHashCode(callSuper = true) @Data // 这里就继承InterfaceInfo,再补充一个调用次数的字段 public class InterfaceInfoVO extends InterfaceInfo { /** * 调用次数 */ private Integer totalNum; private static final long serialVersionUID = 1L; } ``` ![[bw5wDEg2sCTi9hHL-88b64bd6.webp]] 由于我们需要查询,涉及到两个表,user\_interface\_info 表和接口信息表。因此,在这种情况下,我们可以在映射层(Map层)编写一个自定义的 SQL 查询语句,然后在业务层实现下面的关联查询。 找到 UserInterfaceInfoMapper 整个自定义 SQL,把刚刚编写的 SQL 语句粘贴进去。 ![[XVOkzGESphxcKZkA-b3d97e8a.webp]] 编写代码。 ![[LQMy4hl8FeHBNjao-483470aa.webp]] 把光标放到 listTopInvokeInterfaceInfo 中,按[Alt+Enter] 生成 statement。 ![[lJdmfmCenycsZvGH-be785f89.webp]] 自动生成了对应的 map。 ![[eQwliUQXgLqdHwUm-d1e228c3.webp]] 把 SQL 语句粘贴过来。 ![[pdgnTRP5ql837Bla-a32db085.webp]] ![[ehom1LpFnShbic3i-ac1e33ae.webp]] 我们是**如何处理这个业务的呢?**我们并不是直接在代码中编写 SQL,而是先将 SQL 写好,然后在数据库中执行它,确保 SQL 的正确性。然后再开始编写代码,这样可以节省很多时间。 如果你遇到业务逻辑问题,怀疑是数据查询的问题,可以先打印相关日志,并将 SQL 语句复制到数据库中执行一次。这样可以确定是业务代码问题还是 SQL 语句问题。 然后修改一下,把 limit 改成动态参数。 ![[1bHKW3OdQH7SZtnM-1ffbe0c5.webp]] 继续编写 AnalysisController。 ```java package com.yupi.project.controller; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.yupi.project.annotation.AuthCheck; import com.yupi.project.common.BaseResponse; import com.yupi.project.common.ErrorCode; import com.yupi.project.common.ResultUtils; import com.yupi.project.exception.BusinessException; import com.yupi.project.mapper.UserInterfaceInfoMapper; import com.yupi.project.model.vo.InterfaceInfoVO; import com.yupi.project.service.InterfaceInfoService; import com.yupi.yuapicommon.model.entity.InterfaceInfo; import com.yupi.yuapicommon.model.entity.UserInterfaceInfo; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.context.annotation.Configuration; import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * 分析控制器 */ @RestController @RequestMapping("/analysis") @Slf4j public class AnalysisController { @Resource private UserInterfaceInfoMapper userInterfaceInfoMapper; @Resource private InterfaceInfoService interfaceInfoService; /** * 获取调用次数最多的接口信息列表。 * 通过用户接口信息表查询调用次数最多的接口ID,再关联查询接口详细信息。 * * @return 接口信息列表,包含调用次数最多的接口信息 */ @GetMapping("/top/interface/invoke") @AuthCheck(mustRole = "admin") public BaseResponse> listTopInvokeInterfaceInfo() { // 查询调用次数最多的接口信息列表 List userInterfaceInfoList = userInterfaceInfoMapper.listTopInvokeInterfaceInfo(3); // 将接口信息按照接口ID分组,便于关联查询 Map> interfaceInfoIdObjMap = userInterfaceInfoList.stream() .collect(Collectors.groupingBy(UserInterfaceInfo::getInterfaceInfoId)); // 创建查询接口信息的条件包装器 QueryWrapper queryWrapper = new QueryWrapper<>(); // 设置查询条件,使用接口信息ID在接口信息映射中的键集合进行条件匹配 queryWrapper.in("id", interfaceInfoIdObjMap.keySet()); // 调用接口信息服务的list方法,传入条件包装器,获取符合条件的接口信息列表 List list = interfaceInfoService.list(queryWrapper); // 判断查询结果是否为空 if (CollectionUtils.isEmpty(list)) { throw new BusinessException(ErrorCode.SYSTEM_ERROR); } // 构建接口信息VO列表,使用流式处理将接口信息映射为接口信息VO对象,并加入列表中 List interfaceInfoVOList = list.stream().map(interfaceInfo -> { // 创建一个新的接口信息VO对象 InterfaceInfoVO interfaceInfoVO = new InterfaceInfoVO(); // 将接口信息复制到接口信息VO对象中 BeanUtils.copyProperties(interfaceInfo, interfaceInfoVO); // 从接口信息ID对应的映射中获取调用次数 int totalNum = interfaceInfoIdObjMap.get(interfaceInfo.getId()).get(0).getTotalNum(); // 将调用次数设置到接口信息VO对象中 interfaceInfoVO.setTotalNum(totalNum); // 返回构建好的接口信息VO对象 return interfaceInfoVO; }).collect(Collectors.toList()); // 返回处理结果 return ResultUtils.success(interfaceInfoVOList); } } ``` 重启 yuapi-backend 项目,后端整完了~ ### 3. 前端开发(时间点 02:02:39-02:51:38) #### 3.1 图表库(时间点 02:02:39-02:23:14) 接下来我们要着手处理前端部分,我们的目标是在前端页面上展示一个饼图。 那么,**如何展示饼图呢?**一般来说,我们并不需要自己手动绘制饼图,因为已经有现成的图表库可供使用。在大多数情况下,我们会使用现成的图表库,除非你的公司拥有充足的资源来开发自己的图表组件。比如: - [ECharts](https://echarts.apache.org/zh/index.html)(推荐) - [AntV](https://antv.vision/zh)(推荐) - BizCharts ECharts 唯一的缺点是它的外观相对较传统,不够现代化,缺乏一些科技感和精致度,但它的功能却非常强大。 BizCharts 则是一个商业性质的图表库,提供的图表更加华丽。另外,AntV 也是一个值得推荐的选择,相较于 BizCharts,AntV 更被广泛推崇。 **如何使用图表库:** 1. 看官网 2. 找到快速入门、按文档去引入库 3. 进入示例页面 4. 找到你要的图 5. 在线调试 6. 复制代码 7. 改为真实数据 如果是 React 项目,用这个库:[https://github.com/hustcc/echarts-for-react。](https://github.com/hustcc/echarts-for-react%E3%80%82) #### 3.2 AntV 官网浏览(时间点 02:23:14-02:24:15) 访问 AntV 官网 —— [AntV](https://antv.vision/zh)。 ![[IaAkcBNaKZUqq5M0-2c9e47e5.webp]] AntV 是阿里蚂蚁金服开发的一套数据可视化库,用于数据呈现与展示。大家以后需要进行数据可视化,强烈建议使用 AntV。这个库拥有丰富的功能和一系列的产品,其界面设计也相当出色。 AntV 不仅仅是一款数据可视化工具,它还包括了多个子库。 其中,G2 专注于 PC 端图表的展示与绘制; ![[BiDCvC78vY8NUfEn-ec140c3a.webp]] S2 则提供了多维表格的功能,适用于专注于表格数据的场景; ![[AZwER3mc3DcLKnjV-bb74a67a.webp]] G6 则用于处理关系型数据的可视化,尤其适用于图论和模型分析等领域; ![[ueeo1rkI5UpwBmu5-2e921cf2.webp]] 而 X6 则是一套流程编辑引擎,可用于创建流程图和 ER 图等,非常适合构建流程图系统。 ![[x2dvOb2LxS2VwUyB-ca8089cd.webp]] L7 是更高级的数据可视化库,它专注于地理位置城市化、空间城市化以及地理数据的可视化呈现,这使得它在展示地理信息方面具有非常出色的表现。 ![[6OT7h2xzU11yQ8Ik-aaa75574.webp]] F2 则专注于移动端图表的展示与绘制。与之对应的,G2则是适用于 PC 端的图表库,两者在不同终端上都能够提供高效的数据可视化。 ![[V3KQ7EOCF5YQgrR0-ed2cef0f.webp]] AVA 并不是针对普通开发人员的工具。它更适用于对框架进行深度二次开发,或者研发自己的技术框架,需要进行更加复杂的数据分析、定制化数据分析等等。一般来说,大多数开发者并不需要使用 AVA。 ![[ZWq4pN71OJ9yczWt-864d3abf.webp]] 接下来,对于我们的 PC 端,可能会使用 G2 这个库。如何使用呢?后面可能还会给大家更详细地介绍,但本期先给大家快速地看一下,其实所有这些可视化库都有相似的用法: 进入官方网站,然后浏览网站上的图表示例。你可以只专注于图表示例,无需浏览其他内容,直接找到你需要的图表样式。 ![[m50ZPgeJyvSXXD8r-fc3e4575.webp]] 找到饼图,点击进入。 —— [饼图](https://g2.antv.antgroup.com/examples/general/pie/#pie) ![[RynZ5HZFuLhr3O6l-40e2064e.webp]] 右侧有现成的代码供你使用,无需过多考虑,只需要将代码复制粘贴到的项目中。 接着,将后端实际获取到的数据替换掉原始代码中获取数据的部分,这里就不介绍这个库的使用方法,因为它的用法非常简单。所有这些可视化图表库的用法都类似,它们没有太大的学习成本,除非你要深入研究这些内容。 ![[G843E0GZlkW0Z7Ei-df7d719e.webp]] #### 3.3 ECharts 使用(时间点 02:24:15-02:33:54) 访问 ECharts 官网,点击`示例`。 —— [ECharts](https://echarts.apache.org/zh/index.html) ps.百度已将 ECharts 捐赠给了 Apache 基金会。 ![[EzyGI6n8hCzDLW4v-ba84bb13.webp]] 找到 [饼图](https://echarts.apache.org/examples/zh/editor.html?c=pie-simple),就用第一个。 ![[Y4q87hEwj4x2yhjb-2ca92c93.webp]] 可以在这里调试。 ![[hnsNEpdr2wGwvBcQ-e3d24350.webp]] 比如这里改成 '鱼皮'。 ![[mZXgPuDeTq6EbRd3-892ff6cf.webp]] 然后复制代码。 ```java option = { title: { text: 'Referer of a Website', subtext: 'Fake Data', left: 'center' }, tooltip: { trigger: 'item' }, legend: { orient: 'vertical', left: 'left' }, series: [ { name: 'Access From', type: 'pie', radius: '50%', data: [ { value: 1048, name: '鱼皮' }, { value: 735, name: 'Direct' }, { value: 580, name: 'Email' }, { value: 484, name: 'Union Ads' }, { value: 300, name: 'Video Ads' } ], emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)' } } } ] }; ``` 代码先留着,接下来要引入 Echarts。 因为我们前端项目是 react,去 [github](https://github.com/) 找 react 版的,搜索: [react-echarts](https://github.com/hustcc/echarts-for-react)。 ![[9J926oDuohctgj0y-c96f7426.webp]] ![[fFE9aTBgA4KHZev7-c595207d.webp]] 往下滑,复制安装命令。 ![[lf4OwM0PMuil7j92-a62f28db.webp]] ```bash npm install --save echarts-for-react npm install --save echarts ``` 粘贴到前端的终端中执行。 ![[9Anov0YrqaplNy1P-2176fd10.webp]] ![[cM4J0i7ucvdmcqKE-757cde8a.webp]] 安装之后,回到 github 往下看,看它怎么用。 只需要将其引入,引入后,需要使用 ReactECharts 组件,这里有一个 option 选项。 ![[1tuJTar6en3y2JW7-1ca0eb47.webp]] ```python import ReactECharts from 'echarts-for-react'; // render echarts option. ``` option 选项对应我们示例代码中的这里。 ![[iMQwH0THUtPGADfq-8b26455f.webp]] #### 3.4 新建分析页(时间点 02:33:54-02:51:38) 去 routes.ts 增加新的路由。 ![[PlVLQxpNuhQeP9Cr-5edd32f3.webp]] 在 Admin 目录下新建`InterfaceAnalysis`目录,复制 index.tsx,粘贴到新建的目录下。 ![[p6Gx4MKXf2SZdZJR-cc3d607c.webp]] 删除 index.tsx 多余的内容,并修改一下。 ![[PuJAZtwhqYTB49y7-4279f774.webp]] 引入 ECharts。 ![[XsPQUeVSjxAtvSKk-70284893.webp]] 然后把示例代码粘贴进来,修改一下。 ```java import { PageContainer } from '@ant-design/pro-components'; import '@umijs/max'; import React, { useEffect, useState } from 'react'; import ReactECharts from 'echarts-for-react'; /** * 接口分析 * @constructor */ const InterfaceAnalysis: React.FC = () => { // 存储数据的状态 const [data, setData] = useState([]); // 控制加载状态的状态,默认加载中(true) const [loading, setLoading] = useState(true); useEffect(() => { // todo 从远程获取数据 },[]) // ECharts图表的配置选项 const option = { title: { text: 'Referer of a Website', subtext: 'Fake Data', left: 'center' }, tooltip: { trigger: 'item' }, legend: { orient: 'vertical', left: 'left' }, series: [ { name: 'Access From', type: 'pie', radius: '50%', data: [ { value: 1048, name: '鱼皮' }, { value: 735, name: 'Direct' }, { value: 580, name: 'Email' }, { value: 484, name: 'Union Ads' }, { value: 300, name: 'Video Ads' } ], emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)' } } } ] } return ( {/* 使用 ReactECharts 组件,传入图表配置 */} ); }; export default InterfaceAnalysis; ``` 回到前端页面查看效果,成功展示出来了。 ![[lXFreX9hFvE3LoCv-56fcde3a.webp]] 我们刚刚在后端新写了一个接口,在前端用 openapi 生成。 ![[v9OPoNBvVlk5ulZd-f02b7ca2.webp]] 继续编写分析页的代码。 ```java import { PageContainer } from '@ant-design/pro-components'; import '@umijs/max'; import React, {useEffect, useState} from 'react'; import ReactECharts from 'echarts-for-react'; import {listTopInvokeInterfaceInfoUsingGET} from "@/services/yuapi-backend/analysisController"; /** * 接口分析 * @constructor */ const InterfaceAnalysis: React.FC = () => { const [data, setData] = useState([]); useEffect(() => { try { listTopInvokeInterfaceInfoUsingGET().then(res => { if (res.data) { setData(res.data); } }) } catch (e: any) { } // todo 从远程获取数据 }, []) // 映射:{ value: 1048, name: 'Search Engine' }, const chartData = data.map(item => { return { value: item.totalNum, name: item.name, } }) const option = { title: { text: '调用次数最多的接口TOP3', left: 'center', }, tooltip: { trigger: 'item', }, legend: { orient: 'vertical', left: 'left', }, series: [ { name: 'Access From', type: 'pie', radius: '50%', data: chartData, emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)', }, }, }, ], }; return ( ); }; export default InterfaceAnalysis; ``` 回到前端页面查看效果。 ![[pOkoEM9ewAuO4EkW-b7d1acc1.webp]] 如果要改样式,先在 ECharts 调整好,再粘贴到项目中即可。 ![[hnsNEpdr2wGwvBcQ-e3d24350.webp]] 开发完咯~ 下期不再见<( ̄ˇ ̄)/ ## 六、上线计划(时间点 02:51:38-03:03:28) **前端:**参考之前用户中心或伙伴匹配系统的上线方式。 **后端:** - backend 项目:web 项目,部署 spring boot 的 jar 包(对外的) - gateway 网关项目:web 项目,部署 spring boot 的 jar 包(对外的) - interface 模拟接口项目:web 项目,部署 spring boot 的 jar 包(不建议对外暴露的) **关键:网络必须要连通** **自己学习用:**单个服务器部署这三个项目就足够。 如果你是搞大事,多个服务器建议在 **同一内网** ,内网交互会更快、且更安全。 ## 七、扩展思路 1. **用户可以申请更换签名** 2. **怎么让其他用户也上传接口?** - 需要提供一个机制(界面),让用户输入自己的接口 host(服务器地址)、接口信息,将接口信息写入数据库。 - 可以在 interfaceInfo 表里加个 host 字段,区分服务器地址,让接口提供者更灵活地接入系统。 - 将接口信息写入数据库之前,要对接口进行校验(比如检查他的地址是否遵循规则,测试调用),保证他是正常的。 - 将接口信息写入数据库之前遵循咱们的要求(并且使用咱们的 sdk), - 在接入时,平台需要测试调用这个接口,保证他是正常的。 1. **网关校验是否还有调用次数** - 需要考虑并发问题,防止瞬间调用超额。 1. **网关优化** - 比如增加限流 / 降级保护,提高性能等。还可以考虑搭配 Nginx 网关使用。 1. **功能增强** - 可以针对不同的请求头或者接口类型来设计前端界面和表单,便于用户调用,获得更好的体验。 - 可以参考 swagger、postman、knife4j 的页面。 上面提到的要检查用户提供的地址是否符合规则。我们的项目中,所有的模拟接口都是以 /api 开头,或者说,我们规定所有模拟接口的地址都必须以 [http://localhost:8123](http://localhost:8123/)开头。你可以根据需要自行设置规则,然而,在制定这些规则时,最好不要过于严格,最好像之前提到的,可以在数据库中存储一个 host 的信息,只需满足一些特定的后缀条件,比如以 /api 开头等。 这个项目实际上有很多可以发展的方向,有许多要考虑的要点。如果你希望项目取得成功,就需要考虑许多方面。你需要制定规范,关注安全性、性能等方面。此外,还需要考虑如何防止被滥用。 --- **有同学问:Dubbo 不是需要暴露服务才可以吗?** 我们这里不是那个模拟接口用 Dubbo 去暴露的,**我们暴露的是什么?**暴露是系统内部用的接口,什么查询数据库中是否给用户分配密钥、接调用接口次数统计。暴露的不是开发者提供的接口,开发者提供的接我们是通过网关去转发的。然后这里你如果想让网端直接通过同一套地址去调用,那你就让用户遵循这个地址规则。 **有同学问:现在的 SDK 不是固定了方法吗?** 对于这个问题,需要明确一个**核心点:**一旦接口投入使用后,肯定要针对这个接口进行 SDK 的开发。你需要不断地完善 SDK,使其适应不断变化的需求。当然,也可以让 SDK 从接口信息表中读取信息,然后动态生成方法等等,这种做法也是可行的,然而,并不建议这样做。实际上,接口的发布可能不太像我们在应用商店发布应用那样灵活,在这种接口的发布过程中,建议还是介入一点人工。 以腾讯云的 SDK 手册为例,它有许多不同的 SDK,尽管代码可能是自动生成的,但是它的 SDK 是根据接口动态变化的。这意味着它并不是为 100 个接口提供同一个代码供调用,相反,它会为每个方法、每个接口生成相应的方法,同样,它也会根据不同的地址生成相应的方法。这一点非常重要,请大家多去使用一下第三方的 API 接口,这样就会更了解对方的 SDK 是如何设计的。 你可以去腾讯云下载他们的 SDK,会发现其中包含大量方法,基本上每个接口都有相应的方法。这样做的目的是让使用者更加便利,他们只需要输入方法名就能实现操作,无需关心具体的接口地址。 **有同学问:就是每次发布一个接口,都需要更新一次 SDK?** 是的,实际上每次发布一个新的接口,都需要对 SDK 进行更新。**建议的做法**是这样的:由于用户并不关心具体的接口地址,因此你可以让他们直接调用方法名,然后根据这些方法名去动态生成对应的方法。每次发布新接口时,更新 SDK 的操作可以做得非常简单,可以采用脚本的方式,从数据库中读取接口地址,与之前已有的地址进行对比,然后补充相应的方法,这个过程是可行的,事实上,很多公司都在这样做。 --- **项目分区导航**:⬅️ [[08-项目笔记(一)|08-项目笔记(一)]] | 09-项目笔记(七) - API 开放项目项目教程 - 编程导航教程 | ➡️ [[10-项目笔记(三)|10-项目笔记(三)]]